package org.leiyang.service.impl;

import io.netty.util.internal.StringUtil;
import org.leiyang.common.dtos.*;
import org.leiyang.common.holder.MsgServerAndClientHolder;
import org.leiyang.common.redis.ReactiveRedisCache;
import org.leiyang.common.util.TokenUtil;
import org.leiyang.domains.dtos.LoginVO;
import org.leiyang.common.entities.User;
import org.leiyang.domains.dtos.UserList;
import org.leiyang.repository.UserRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.util.function.Tuple2;
import reactor.util.function.Tuples;

import java.util.Collections;
import java.util.Objects;
import java.util.Optional;

import static org.leiyang.common.util.Constants.*;

@Service
public class UserService {
    private final Logger logger = LoggerFactory.getLogger(getClass());
    private final UserRepository userRepository;
    private final ReactiveRedisCache redisCache;
    private final MsgServerAndClientHolder msgServerAndClientHolder;
    private final RemoteCallImpl remoteCall;

    public UserService(UserRepository userRepository, ReactiveRedisCache redisCache,
                       MsgServerAndClientHolder msgServerAndClientHolder, RemoteCallImpl remoteCall) {
        this.userRepository = userRepository;
        this.redisCache = redisCache;
        this.msgServerAndClientHolder = msgServerAndClientHolder;
        this.remoteCall = remoteCall;
    }

    /**
     * 获取所有的用户(当前用户除外)
     * @param currentUserId 当前登录用户的ID
     */
    public Flux<UserList> listAllUsers(Long currentUserId) {
        return userRepository.findAll().filter(user -> !Objects.equals(user.getId(), currentUserId))
            .map(user -> UserList.builder().id(user.getId()).sex(user.getSex())
                .avater(user.getAvater()).username(user.getUsername())
                .hMsgCount(0)
                .build()
        );
    }

    /**
     * 登录接口(简易版)
     * 规则： 同一个出口公网IP下同一个用户只能在一个设备上同时在线
     * @param vo 登录入参
     * @return token
     */
    public Mono<R<String>> login(LoginVO vo, String clientIp) {
        String username = vo.getUsername();
        String password = vo.getPassword();

        if(StringUtil.isNullOrEmpty(username) || StringUtil.isNullOrEmpty(password)) {
            throw new RuntimeException("用户名或密码不能为空.");
        }
        //查询用户信息
        Mono<User> userFindMono = userRepository.findUserByName(username);
        //生成token
        Mono<Tuple2<User, String>> tokenCreateMono = userFindMono
            .switchIfEmpty(Mono.error(new RuntimeException("用户名不存在.")))
            .flatMap(user -> {
                if(!Objects.equals(password, user.getPassword())) {
                    return Mono.error(new RuntimeException("密码不正确!"));
                }
                return msgServerAndClientHolder.confirmClientOnline(clientIp, user.getId())
                    .flatMap(isExist -> {
                        //如果存在，则拦截本次登录
                        if(isExist) {
                            return Mono.error(new RuntimeException("该账号已在其他设备上登录，请先下线!"));
                        }
                        return Mono.just(user);
                    });
            })
            .map(user ->
                Tuples.of(user, Objects.requireNonNull(TokenUtil.createAccessToken(user.getUsername(), user.getId())))
            );
        //存储用户信息
        return tokenCreateMono.flatMap(tuple2 -> {
            LoginUser loginUser = LoginUser.builder().token(tuple2.getT2()).username(tuple2.getT1().getUsername())
                    .userid(tuple2.getT1().getId()).build();
            return redisCache.setCacheObject(ACCESS_TOKEN.concat(tuple2.getT2()), loginUser, EXPIRATION)
                    .map(save -> save ? R.ok(tuple2.getT2()) : R.fail());
        });
    }

    /**
     * 获取指定用户的用户信息
     * @param uId 用户ID
     */
    public Mono<R<UserInfoDTO>> getCurrentUserInfo(Long uId) {
        return userRepository.listUsersByIds(Collections.singletonList(String.valueOf(uId)))
            .elementAt(0, User.builder().build())
            .map(user -> {
                logger.info("getCurrentUserInfo: {}", user);
                if(Objects.nonNull(user.getId())) {
                    return R.ok(UserInfoDTO.builder().uid(user.getId())
                            .username(user.getUsername()).face(user.getAvater()).build()
                    );
                }
                return R.fail("当前用户信息不存在!");
            });
    }

    /**
     * 退出登录
     * 先删除token，然后再断开与msgServer的连接
     * @param accessToken token
     * @param clientIp 客户端出口IP
     * @param uid 用户ID
     * @return 是否成功退出登录
     */
    public Mono<R<Boolean>> logout(String accessToken, String clientIp, Long uid) {
        Mono<ConnectedMsgServerInfo> msgServersFlux = msgServerAndClientHolder.msgServerByClientIp(uid, clientIp);
        return msgServersFlux.map(Optional::of)
            .defaultIfEmpty(Optional.empty())
            .flatMap(connectedMsgServerInfoOptional -> {
                //表示已经与MsgServer断连
                if(!connectedMsgServerInfoOptional.isPresent()) {
                    return Mono.just(true);
                }
                ConnectedMsgServerInfo connectedMsgServerInfo = connectedMsgServerInfoOptional.get();
                String clientId = uid + "@" + clientIp.replaceAll("\\.", "");
                MsgServerInfo msgServerInfo = connectedMsgServerInfo.getMsgServerInfo();
                return redisCache.getCacheObject(MSG_SER_WEB_INFO.concat(msgServerInfo.getServerAddress()).concat(":").concat(msgServerInfo.getServerPort()))
                    .map(addPortObj -> {
                        if(Objects.isNull(addPortObj)) {
                            logger.error("未获取到对应的MsgServer地址信息!");
                        }
                        return String.valueOf(addPortObj);
                    })
                    .flatMap(addPort -> remoteCall.removeOfflineClient(addPort, clientId));
            }).flatMap(remove -> {
                logger.info("执行removeOfflineClient的结果: {}, accessToken: {}", remove, accessToken);
                if(remove) {
                    return redisCache.deleteObject(ACCESS_TOKEN.concat(accessToken));
                }
                return Mono.just(0L);
            }).flatMap(remNum -> (remNum > 0L) ? Mono.just(R.ok(Boolean.TRUE)) : Mono.just(R.fail()));
    }

    /**
     * 对token进行续期
     * 注：暂时不加入自动刷新token有效期的逻辑
     */
    public Mono<Void> refreshToken(LoginUser loginUser) {
        return redisCache.setCacheObject(ACCESS_TOKEN.concat(loginUser.getToken()), loginUser, EXPIRATION)
                .flatMap(refreshRe -> Mono.empty());
    }
}